package com.example.mdc;

import com.alibaba.fastjson2.JSON;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.lang.reflect.Method;

/**
 * @author lbwxxc
 * @date 2025/4/1 20:38
 * @description: 切面
 */
@Data
@Slf4j
@Aspect
@Component
public class MdcAspect implements ApplicationContextAware {

    private ExpressionParser parser = new SpelExpressionParser();
    private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();

    // Spring 应用上下文，管理 Bean 的容器
    private ApplicationContext applicationContext;

    //切点
    @Pointcut("@annotation(MdcDot) || @within(MdcDot)")
    public void getLogAnnotation() {
    }

    //通知
    @Around("getLogAnnotation()")
    public Object handle(ProceedingJoinPoint joinPoint) throws Throwable {

        long start = System.currentTimeMillis();
        boolean hasTag = addMdcCode(joinPoint);
        String className = joinPoint.getTarget().getClass().getSimpleName();
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        String argsJson = null;

        if (args.length > 0) {
            Object firstArg = args[0];
            if (firstArg instanceof MultipartFile file) {
                argsJson = "{\"fileName\": \"" + file.getOriginalFilename() + "\", \"size\": " + file.getSize() + "}";
            } else {
                argsJson = JSON.toJSONString(firstArg);
            }
        }

        try {
            // 打印请求相关参数
            log.info("====== 请求开始: [{}], 入参: {}, 请求类: {}, 请求方法: {} =================================== ",
                    MdcUtil.get("bizCode"), argsJson, className, methodName);
            Object ans = joinPoint.proceed();
            // 打印出参等相关信息
            log.info("====== 请求结束: [{}], 耗时: {}ms, 出参: {} =================================== ",
                    MdcUtil.get("bizCode"), System.currentTimeMillis() - start, JSON.toJSONString(ans));
            return ans;
        } finally {
            if (hasTag) {
                MdcUtil.reset();
            }
        }
    }

    /**
     * @param: joinPoint
     * @return: boolean
     * @description: 检查是否有 MdcDot 注解，如果有则将 bizCode 以及对应的值放入 mdc 中
     */
    private boolean addMdcCode(ProceedingJoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        MdcDot dot = method.getAnnotation(MdcDot.class);
        if (dot == null) {
            dot = (MdcDot) joinPoint.getSignature().getDeclaringType().getAnnotation(MdcDot.class);
        }

        if (dot != null) {
            String bizCode = loadBizCode(dot.bizCode(), joinPoint);
            MdcUtil.add("bizCode", bizCode);
            return true;
        }
        return false;
    }

    /**
     * @param: key
     * @param: joinPoint
     * @return: java.lang.String
     * @description: loadBizCode方法用于解析@MdcDot注解的bizCode元素的值
     */
    private String loadBizCode(String key, ProceedingJoinPoint joinPoint) {
        if (StringUtils.isBlank(key)) {
            return "";
        }

        StandardEvaluationContext context = new StandardEvaluationContext();

        context.setBeanResolver(new BeanFactoryResolver(applicationContext));
        String[] params = parameterNameDiscoverer.getParameterNames(((MethodSignature) joinPoint.getSignature()).getMethod());
        Object[] args = joinPoint.getArgs();
        for (int i = 0; i < args.length; i++) {
            // 获取目标方法的参数名称和参数值，并将它们添加到上下文中
            context.setVariable(params[i], args[i]);
        }
        try {
            parser.parseExpression(key).getValue(context, String.class);
            String res = key;
            return res;
        } catch (Exception e) {
            log.error("解析 SpEL 表达式出错: {}", key, e);
            return "";
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}
